using System;
using System.Net.Sockets;
using System.Diagnostics;

namespace BassMati.Client
{
	/// <summary>
	/// Represents a connection to the server.
	/// </summary>
	public class Client
	{
		#region - Constructors -

		public Client ( )
		: this ( "127.0.0.1", 8080 )
		{ }

		public Client ( string hostName, int portNumber )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.ctor enter" );
			Trace.Indent ( );
			this.portNumber = portNumber;
			this.hostName = hostName;
			Trace.Unindent ( );
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client .ctor exit" );
		}

		#endregion - Constructors -

		#region - Fields -

		protected int portNumber;
		protected string hostName;  // DNS name or IP address.
		protected TcpClient tcpClient;
		protected bool connected;
		protected System.Diagnostics.TraceSwitch traceLevel = new TraceSwitch( "ClientConnectionTraceLevel", "Controls trace information output level." );

		#endregion - Fields -

		#region - Properties -

		/// <summary>
		/// Returns true if the client is connected to the server.
		/// </summary>
		public bool Connected { get { return this.connected; } }

		/// <summary>
		/// Returns the port number of the connection.
		/// </summary>
		public int PortNumber { get { return this.portNumber; } }
		
		/// <summary>
		/// Returns the host name.
		/// </summary>
		public string HostName { get { return this.hostName; } }

		#endregion - Properties -
		
		#region - Methods -

		public void Connect ( )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Open enter", "Info (Client.exe)" );
			Trace.Indent ( );
			try
			{
				if ( this.connected ) throw new System.InvalidOperationException( "Client is already connected." );			
				Trace.WriteLine ( "Creating TcpClient.", "Info (Client.exe)" );
				this. tcpClient = new TcpClient ( this.hostName, this.portNumber );
				Trace.WriteLine ( "TcpClient created.", "Info (Client.exe)" );
				this.connected = true;
			}
			catch ( System.Exception ex )
			{
				// System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it.
				// --> The server may be down.
				Trace.WriteLine ( TraceFormatter.Format( ex), "Error (Client.exe)" );
				throw ( ex );
			}
			finally
			{
				Trace.Unindent ( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Open exit", "Info (Client.exe)" );
			}
		}

		public void Disconnect ( )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Close enter", "Info (Client.exe)" );
			Trace.Indent ( );
			if ( this.connected && this.tcpClient != null )
			{
				Trace.WriteLineIf( traceLevel.TraceInfo, "Closing network stream.", "Info (Client.exe)" );
				this.tcpClient.GetStream().Close();
				Trace.WriteLineIf( traceLevel.TraceVerbose, "Network stream closed.", "Info (Client.exe)" );
				Trace.WriteLineIf( traceLevel.TraceInfo, "Closing TcpClient.", "Info (Client.exe)" );
				this.tcpClient.Close();
				Trace.WriteLineIf( traceLevel.TraceVerbose, "TcpClient closed.", "Info (Client.exe)" );
				this.connected = false;
			}
			Trace.Unindent ( );
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Close  exit", "Info (Client.exe)" );
		}

		/// <summary>
		/// Sends a message to the server.
		/// </summary>
		/// <param name="message"></param>
		/// <remarks>The server must be connected.</remarks>
		/// 
		public void Send ( string message )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Send enter", "Info (Client.exe)" );
			Trace.Indent ( );
			try
			{
				if ( ! this.connected ) throw new System.InvalidOperationException( "Client is not connected." );
				Trace.WriteLineIf( traceLevel.TraceInfo, "Encoding client data.", "Info (Client.exe)" );
				Byte [ ] data = System.Text.Encoding.ASCII.GetBytes ( message );
				Trace.WriteLineIf( traceLevel.TraceInfo, "Writing client data.", "Info (Client.exe)" );
				NetworkStream stream = tcpClient.GetStream ( );
				stream.Write ( data, 0, data.Length );
				Trace.WriteLineIf( traceLevel.TraceInfo, string.Format ( "Sent: {0}", message ), "Info (Client.exe)");
			}
			catch ( System.Exception ex )
			{
				Trace.WriteLine ( TraceFormatter.Format( ex ), "Error (Client.exe)" );
				throw ( ex );
			}
			finally
			{
				Trace.Unindent ( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Send exit", "Info (Client.exe)" );
			}
		}

		public string Receive ( )
		{
			Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Receive enter", "Info (Client.exe)" );
			Trace.Indent ( );
			string data = string.Empty;
			try
			{
				if ( ! this.connected ) throw new System.InvalidOperationException( "Client is not connected." );
				Trace.WriteLineIf( traceLevel.TraceInfo, "Reading data from server.", "Info (Client.exe)");
				NetworkStream stream = this.tcpClient.GetStream ( );
				Byte [ ] buffer = new Byte [ this.tcpClient.ReceiveBufferSize ];	
				int count = stream.Read ( buffer, 0, buffer.Length );
				Trace.WriteLineIf( traceLevel.TraceInfo, "Converting data from server.", "Info (Client.exe)");
				data = System.Text.Encoding.ASCII.GetString ( buffer, 0, count );
			}
			catch ( System.Exception ex )
			{
				Trace.WriteLine ( TraceFormatter.Format( ex ), "Error (Client.exe)" );
				throw ( ex );
			}
			finally
			{
				Trace.Unindent ( );
				Trace.WriteLineIf ( traceLevel.TraceInfo, "Client.Receive exit", "Info (Client.exe)" );
			}
			return data;
		}

		#endregion - Methods -
	}
}